home *** CD-ROM | disk | FTP | other *** search
/ MacAddict 108 / MacAddict108.iso / Software / Internet & Communication / JunkMatcher 1.5.5.dmg / JunkMatcher.app / Contents / Resources / Engine / HTMLBody.py < prev    next >
Encoding:
Python Source  |  2005-06-01  |  10.8 KB  |  287 lines

  1. #
  2. #  HTMLBody.py
  3. #  JunkMatcher
  4. #
  5. #  Created by Benjamin Han on 2/1/05.
  6. #  Copyright (c) 2005 Benjamin Han. All rights reserved.
  7. #
  8.  
  9. # This program is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU General Public License
  11. # as published by the Free Software Foundation; either version 2
  12. # of the License, or (at your option) any later version.
  13.  
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. # GNU General Public License for more details.
  18.  
  19. # You should have received a copy of the GNU General Public License
  20. # along with this program; if not, write to the Free Software
  21. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  22.  
  23. #!/usr/bin/env python
  24.  
  25. import htmlentitydefs, urllib
  26.  
  27. from consts import *
  28. from utilities import *
  29. from GlobalObjects import *
  30. from HTMLEncoding import *
  31.  
  32.  
  33. _TAG_FOR_HIDDEN_URL = u''
  34.  
  35. _entityPat = re.compile(r'&#?\w+;')
  36. _tagPat = re.compile(r'<\s*([^>\s]+)([^>]*)>')
  37. _vacuousTagPat = re.compile(r'(?s)(\S)<([^>/\s]+)([^>]*)>\s*</([^>/\s]+)[^>]*>(\S)')
  38. _attrPat = re.compile(r'(?i)(?:([^"\'<>=\s]+)\s*=\s*["\']?\s*)?([^"\'<>\s]+)["\']?')
  39.  
  40. _ELINKS_ARGS='-force-html -auto-submit 0 -dump 1 -no-home 1 -stdin 1 -dump-charset utf8'
  41.  
  42. _htmlEncodingExtractor = HTMLEncodingExtractor()
  43. _htmlFormatter = HTMLFormatter()
  44.  
  45. name2codepoint = htmlentitydefs.name2codepoint
  46.     
  47.  
  48. def _translateEntities (mo):
  49.     en = mo.group(0)
  50.     if en.startswith('&#'):
  51.         if en[2] == 'x':
  52.             try:
  53.                 return unichr(int(en[3:-1], 16))
  54.             except:
  55.                 return en
  56.         else:
  57.             try:
  58.                 return unichr(int(en[2:-1]))
  59.             except:
  60.                 return en
  61.     else:
  62.         c = name2codepoint.get(en[1:-1])
  63.         if c is None: return en
  64.         else: return unichr(c)
  65.  
  66.  
  67. class _ReplaceHiddenURL (object):
  68.     # improving performance by not having __dict__
  69.     __slots__ = ('urlDict', 'spanList', 'base', 'attrs')
  70.  
  71.     def __init__ (self):
  72.         self.urlDict = {}   # a dict of valid (non-hidden) URLs
  73.         self.spanList = []  # list of tuples (start,end) in the original HTML
  74.     def __call__ (self, mo):
  75.         """Must be called after self.base and self.attrs (a sets.Set) is set!"""
  76.         attr = mo.group(1)
  77.         value = mo.group(2).strip()
  78.         httpMO = httpPat.search(value)
  79.         
  80.         if httpMO:
  81.             # it's an URL
  82.             if self.attrs is not None and attr is not None and attr.lower() in self.attrs:
  83.                 url = urllib.unquote(httpMO.group(0)).lower()  # unquote the URL
  84.                 self.urlDict[url] = self.urlDict.get(url, 0) + 1
  85.                 return '%s="%s"' % (attr, url)
  86.             else:
  87.                 self.spanList.append((mo.start(0) + self.base, mo.end(0) + self.base))
  88.                 return _TAG_FOR_HIDDEN_URL
  89.         else:
  90.             # it's not an URL
  91.             if attr:
  92.                 return '%s="%s"' % (attr, value)
  93.             else:
  94.                 return value
  95.  
  96.  
  97. class _ReplaceBadTag (object):
  98.     # improving performance by not having __dict__
  99.     __slots__ = ('replaceHiddenURL', 'spanList')
  100.  
  101.     def __init__ (self):
  102.         self.replaceHiddenURL = _ReplaceHiddenURL()
  103.         self.spanList = []
  104.     def __call__ (self, mo):
  105.         tag = mo.group(1).lower()
  106.         base = mo.start(2)  # for replaceHiddenURL.spanList
  107.         self.replaceHiddenURL.base = base
  108.         
  109.         attributes = mo.group(2)
  110.         if attributes is not None:
  111.             attributes = attributes.lower()
  112.             if len(attributes.strip()) == 0: attributes = None
  113.         
  114.         if tag[0] == '/':
  115.             # it's an end tag
  116.             if tag[1:] in globalObjects.htmlTags:
  117.                 if attributes is not None:
  118.                     # we don't need to unquote the http strings cuz we just want to identify
  119.                     # it's an URL
  120.                     for moIter in filter(lambda mo: httpPat.match(mo.group(2).strip()),
  121.                                          _attrPat.finditer(attributes)):
  122.                         # yes the value is an URL
  123.                         self.replaceHiddenURL.spanList.append((moIter.start(2) + base,
  124.                                                                moIter.end(2) + base))
  125.                 return '<%s>' % tag
  126.             else:
  127.                 # a bad tag
  128.                 self.spanList.append((mo.start(0), mo.end(0)))
  129.                 return ''
  130.             
  131.         elif tag.startswith('!--'):
  132.             # don't count comments as bad tags
  133.             return ''
  134.  
  135.         # it's a starting tag
  136.         attrs = globalObjects.htmlTags.get(tag, False)
  137.         if attrs is not False:
  138.             if attrs is None:
  139.                 # no URL is allowed in attributes
  140.                 if attributes is None: return '<%s>' % tag
  141.                 else:
  142.                     self.replaceHiddenURL.attrs = None
  143.                     attributes = _attrPat.sub(self.replaceHiddenURL, attributes)
  144.                     return '<%s %s>' % (tag, attributes)
  145.             else:
  146.                 # URL is allowed only after a certain attribute name                
  147.                 if attributes is None:
  148.                     return '<%s>' % tag
  149.                 else:
  150.                     # if attrs is '*' we don't check for hidden URLs
  151.                     if isinstance(attrs, sets.Set):
  152.                         self.replaceHiddenURL.attrs = attrs
  153.                         attributes = _attrPat.sub(self.replaceHiddenURL, attributes)
  154.                     return '<%s %s>' % (tag, attributes)
  155.         else:
  156.             # a bad tag
  157.             self.spanList.append((mo.start(0), mo.end(0)))
  158.             return ''
  159.  
  160.  
  161. class _ReplaceVacuousTag (object):
  162.     # improving performance by not having __dict__
  163.     __slots__ = ('urlDict', 'spanList')
  164.  
  165.     def __init__ (self, urlDict):
  166.         self.urlDict = urlDict
  167.         self.spanList = []
  168.     def __call__ (self, mo):
  169.         if mo.group(2).lower() == mo.group(4).lower():
  170.             # remove URL, if any, from urlDict
  171.             httpMO = httpPat.search(mo.group(3))
  172.             if httpMO:
  173.                 url = httpMO.group(0).lower()
  174.                 if self.urlDict.has_key(url):
  175.                     self.urlDict[url] -= 1
  176.             
  177.             self.spanList.append((mo.end(1), mo.start(5)))
  178.             return ''.join((mo.group(1), mo.group(5)))
  179.         else:
  180.             return mo.group(0)
  181.  
  182.  
  183. class HTMLBody (object):
  184.     """
  185.     An HTML message body
  186.     --------------------
  187.     I. the following are set by __init__()
  188.  
  189.     htmlSrc: the raw source, in Unicode
  190.     encoding: the encoding of the HTML message (could be None)
  191.  
  192.     contentWithoutEntities: the content without entities, in Unicode
  193.     contentWithoutBadTags: the content without bad tags/hidden URLs, in Unicode
  194.     content: final content without vacuous tags, in Unicode
  195.     
  196.     urlDict: a dict of (URL, count) tuples; URLs are unquoted and count could be 0
  197.     
  198.     hiddenURLList:  a list of (start, end) index tuples for identified hidden URLs
  199.                     (w.r.t. contentWithoutEntities)
  200.     badTagList:     a list of (start, end) index tuples for identified bad tags
  201.                     (w.r.t. contentWithoutEntities)
  202.     vacuousTagList: a list of (start, end) index tuples for identified vacuous tags
  203.                     (w.r.t. contentWithoutBadTags)
  204.  
  205.     II. others:
  206.  
  207.     rendering: set by setRendering() (via elinks).
  208.     """
  209.     # improving performance by not having __dict__
  210.     __slots__ = ('htmlSrc', 'encoding',
  211.                  'contentWithoutEntities', 'contentWithoutBadTags', 'content',
  212.                  'urlDict', 'hiddenURLList', 'badTagList', 'vacuousTagList', 'rendering')
  213.         
  214.     def __init__ (self, htmlSrc, defaultEncoding = None):
  215.         """htmlSrc must be raw data (no encoding)."""
  216.         # get the encoding (charset in the meta tag)
  217.         encoding = _htmlEncodingExtractor.extract(htmlSrc)
  218.         if encoding:
  219.             # correct possible mispellings
  220.             mispelling = charsetMispellings.get(encoding)
  221.             if mispelling: encoding = mispelling
  222.             self.encoding = encoding
  223.         else:
  224.             self.encoding = defaultEncoding
  225.  
  226.         # self.encoding can fall back to the defaultEncoding,
  227.         # but encoding is *extracted* from htmlSrc, which can be None
  228.  
  229.         htmlSrc = decodeText(htmlSrc, self.encoding)                   # decode into Unicode
  230.         self.htmlSrc = _htmlFormatter.format(htmlSrc,                  # rewrite/insert charset
  231.                                              encoding is None,
  232.                                              _htmlEncodingExtractor.hasTagHTML,
  233.                                              _htmlEncodingExtractor.hasTagHead)
  234.         htmlSrc = _entityPat.sub(_translateEntities, htmlSrc).strip()  # rid of entities
  235.         
  236.         # At this point: both htmlSrc and self.htmlSrc are in Unicode; their differences:
  237.         # 1. self.htmlSrc keeps all entities, but htmlSrc doesn't
  238.         # 2. in the meta tag, self.htmlSrc always has charset=utf8, but htmlSrc keeps the
  239.         #    original.
  240.         # 3. self.htmlSrc will be fed to elinks (which can't deal with lots of charsets),
  241.         #    and htmlSrc will be used to produce other content* attributes
  242.  
  243.         self.contentWithoutEntities = htmlSrc
  244.         
  245.         # cleaning bad tags and hidden URLs
  246.         replaceBadTag = _ReplaceBadTag()
  247.         self.contentWithoutBadTags = _tagPat.sub(replaceBadTag, htmlSrc).strip()
  248.         self.hiddenURLList = replaceBadTag.replaceHiddenURL.spanList
  249.         self.urlDict = replaceBadTag.replaceHiddenURL.urlDict
  250.         self.badTagList = replaceBadTag.spanList
  251.  
  252.         # removing vacuous tags
  253.         replaceVacuousTag = _ReplaceVacuousTag(self.urlDict)
  254.         self.content = _vacuousTagPat.sub(replaceVacuousTag, self.contentWithoutBadTags).strip()
  255.         self.vacuousTagList = replaceVacuousTag.spanList
  256.  
  257.     def setRendering (self):
  258.         """Returns HTML rendering using elinks."""
  259.         if hasattr(self, 'rendering'): return self.rendering
  260.  
  261.         # invoke elinks
  262.         try:
  263.             #print encodeText(self.htmlSrc)            
  264.             elinksIn, elinksOut = os.popen2('"%s"elinks %s'%(BIN_PATH, _ELINKS_ARGS))
  265.             elinksIn.write(encodeText(self.htmlSrc))
  266.             elinksIn.close()
  267.             self.rendering = decodeText(elinksOut.read(), 'utf8') # do NOT strip()!
  268.             elinksOut.close()
  269.             
  270.         except:
  271.             self.rendering = u'[executing elinks caused unknown errors]'
  272.     
  273.  
  274. if __name__ == '__main__':
  275.    import sys
  276.    
  277.    if len(sys.argv) == 1:
  278.        print 'Usage: ./HTMLBody.py <filename>'
  279.        print '   * filename is the name of the file containing HTML raw source.'
  280.        sys.exit(1)
  281.  
  282.    htmlBody = HTMLBody(open(sys.argv[1]).read())
  283.    print encodeText(htmlBody.content)
  284.  
  285.    htmlBody.setRendering()
  286.    print encodeText(htmlBody.rendering)
  287.